using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Diagnostics.Contracts;
using Konst3d.Efficiency.IO;

// LAYOUT:
// signature    4b (uint)    0
// version      4b (uint)    4
// count        4b (int)     8
// reserved   400b (...)    12
// <entries>       (...)   412

// ENTRY LAYOUT:
// created year     4b (int)     0   // if (year == -1) => cell is free
// created month    1b (byte)    4
// created day      1b (byte)    5
// dead year        4b (int)     6
// dead month       1b (byte)   10
// dead day         1b (byte)   11
// severity         1b (byte)   12
// state            1b (byte)   13
// title ID         4b (int)    14
// descr ID         4b (int)    18

namespace Konst3d.Efficiency.Suite.SparsedBinary
{
    internal class TaskFile: UnorderedListFile<TaskFile.Entry>
    {
        #region Constants

        private const uint SIGNATURE = 0x46464646;
        private const uint VERSION = 1;
        private const int OFFSET_CAPACITY = 8;
        private const int OFFSET_DATA = 412;
        private const int OFFSET_ENTRY_CREATED_YEAR = 0;
        private const int OFFSET_ENTRY_CREATED_MONTH = 4;
        private const int OFFSET_ENTRY_CREATED_DAY = 5;
        private const int OFFSET_ENTRY_DEAD_YEAR = 6;
        private const int OFFSET_ENTRY_DEAD_MONTH = 10;
        private const int OFFSET_ENTRY_DEAD_DAY = 11;
        private const int OFFSET_ENTRY_SEVERITY = 12;
        private const int OFFSET_ENTRY_STATE = 13;
        private const int OFFSET_ENTRY_TITLE_ID = 14;
        private const int OFFSET_ENTRY_DESCR_ID = 18;
        private const int ENTRY_BYTE_SIZE = 22;

        #endregion // Constants

        #region Types

        public struct Entry
        {
            public int TitleID;
            public int DescrID;
            public byte Severity;
            public byte State;
            public DateTime Created;
            public DateTime DeadLine;
        }

        #endregion // Types

        #region Create/Open Methods

        public static TaskFile Create(string path, bool overwrite)
        {
            Contract.Requires(!String.IsNullOrEmpty(path));

            if (File.Exists(path) && !overwrite)
            {
                throw new IOException(String.Format("File \"{0}\" is already exists.", path));
            }

            FileStream stream = null;
            BinaryReader reader = null;
            BinaryWriter writer = null;

            try
            {
                stream = File.Open(path, FileMode.Create, FileAccess.ReadWrite, FileShare.Read);
                reader = new BinaryReader(stream, Encoding.UTF8);
                writer = new BinaryWriter(stream, Encoding.UTF8);

                writer.Write(SIGNATURE);
                writer.Write(VERSION);
                writer.Write((int)0);
                writer.WriteBytes(0, 400);

                writer.Flush();
                writer.Seek(0, SeekOrigin.Begin);

                return new TaskFile(stream, reader, writer);
            }
            catch
            {
                if (stream != null)
                {
                    stream.Dispose();
                }

                if (reader != null)
                {
                    reader.Dispose();
                }

                if (writer != null)
                {
                    writer.Dispose();
                }

                throw;
            }
        }

        public static TaskFile Open(string path)
        {
            Contract.Requires(!String.IsNullOrEmpty(path));
            Contract.Requires(File.Exists(path));

            FileStream stream = null;
            BinaryReader reader = null;
            BinaryWriter writer = null;

            try
            {
                stream = File.Open(path, FileMode.Open, FileAccess.ReadWrite, FileShare.Read);
                reader = new BinaryReader(stream, Encoding.UTF8);
                writer = new BinaryWriter(stream, Encoding.UTF8);

                return new TaskFile(stream, reader, writer);
            }
            catch
            {
                if (stream != null)
                {
                    stream.Dispose();
                }

                if (reader != null)
                {
                    reader.Dispose();
                }

                if (writer != null)
                {
                    writer.Dispose();
                }

                throw;
            }
        }

        #endregion // Create/Open Methods

        #region Constructor

        private TaskFile(FileStream stream, BinaryReader reader, BinaryWriter writer):
            base(stream, reader, writer, SIGNATURE, VERSION)
        {
        }

        #endregion // Constructor

        #region UnorderedListFile<TaskFile2.Entry> Members

        protected override long CapacityOffset
        {
            get
            {
                return OFFSET_CAPACITY;
            }
        }

        protected override long DataOffset
        {
            get
            {
                return OFFSET_DATA;
            }
        }

        protected override int EntryByteSize
        {
            get
            {
                return ENTRY_BYTE_SIZE;
            }
        }

        protected override TaskFile.Entry ReadData(long offset = -1, SeekOrigin origin = SeekOrigin.Begin)
        {
            if (offset >= 0)
            {
                _writer.Seek((int)offset, origin);
            }

            var entry = new Entry();

            entry.Created   = _reader.ReadShortDate().GetDateTime();
            entry.DeadLine  = _reader.ReadShortDate().GetDateTime();
            entry.Severity  = _reader.ReadByte();
            entry.State     = _reader.ReadByte();
            entry.TitleID   = _reader.ReadInt32();
            entry.DescrID   = _reader.ReadInt32();

            return entry;
        }

        protected override void WriteData(TaskFile.Entry entry, long offset = -1, SeekOrigin origin = SeekOrigin.Begin)
        {
            if (offset >= 0)
            {
                _writer.Seek((int)offset, origin);
            }

            _writer.WriteShortDate(entry.Created);
            _writer.WriteShortDate(entry.DeadLine);
            _writer.Write(entry.Severity);
            _writer.Write(entry.State);
            _writer.Write(entry.TitleID);
            _writer.Write(entry.DescrID);

            _writer.Flush();
        }

        #endregion // UnorderedListFile<TaskFile2.Entry> Members

        #region Public Methods

        public int AddEntry(int titleID, int descrID, byte severity, byte state, DateTime created, DateTime deadLine)
        {
            Contract.Assert(Capacity < int.MaxValue);

            return AddEntry(new Entry() { Created = created, DeadLine = deadLine, TitleID = titleID, DescrID = descrID, Severity = severity, State = state });
        }

        public int GetEntryTitleID(int id)
        {
            Contract.Requires(id >= 0 && id < Capacity);

            return _reader.ReadInt32(GetEntryDataOffset(id) + OFFSET_ENTRY_TITLE_ID);
        }

        public void SetEntryTitleID(int id, int titleID)
        {
            Contract.Requires(id >= 0 && id < Capacity);

            _writer.Write(titleID, (int)GetEntryDataOffset(id) + OFFSET_ENTRY_TITLE_ID);
            _writer.Flush();
        }

        public int GetEntryDescrID(int id)
        {
            Contract.Requires(id >= 0 && id < Capacity);

            return _reader.ReadInt32(GetEntryDataOffset(id) + OFFSET_ENTRY_DESCR_ID);
        }

        public void SetEntryDescrID(int id, int descrID)
        {
            Contract.Requires(id >= 0 && id < Capacity);

            _writer.Write(descrID, (int)GetEntryDataOffset(id) + OFFSET_ENTRY_DESCR_ID);
            _writer.Flush();
        }

        public byte GetEntrySeverity(int id)
        {
            Contract.Requires(id >= 0 && id < Capacity);

            return _reader.ReadByte(GetEntryDataOffset(id) + OFFSET_ENTRY_SEVERITY);
        }

        public void SetEntrySeverity(int id, byte severity)
        {
            Contract.Requires(id >= 0 && id < Capacity);

            _writer.Write(severity, (int)GetEntryDataOffset(id) + OFFSET_ENTRY_SEVERITY);
            _writer.Flush();
        }

        public byte GetEntryState(int id)
        {
            Contract.Requires(id >= 0 && id < Capacity);

            return _reader.ReadByte(GetEntryDataOffset(id) + OFFSET_ENTRY_STATE);
        }

        public void SetEntryState(int id, byte state)
        {
            Contract.Requires(id >= 0 && id < Capacity);

            _writer.Write(state, (int)GetEntryDataOffset(id) + OFFSET_ENTRY_STATE);
            _writer.Flush();
        }

        public DateTime GetEntryCreated(int id)
        {
            Contract.Requires(id >= 0 && id < Capacity);

            return _reader.ReadShortDate(GetEntryDataOffset(id) + OFFSET_ENTRY_CREATED_YEAR).GetDateTime();
        }

        public void SetEntryCreated(int id, DateTime created)
        {
            Contract.Requires(id >= 0 && id < Capacity);

            _writer.WriteShortDate(created, (int)GetEntryDataOffset(id) + OFFSET_ENTRY_CREATED_YEAR);
        }

        public DateTime GetEntryDeadLine(int id)
        {
            Contract.Requires(id >= 0 && id < Capacity);

            return _reader.ReadShortDate(GetEntryDataOffset(id) + OFFSET_ENTRY_DEAD_YEAR).GetDateTime();
        }

        public void SetEntryDeadLine(int id, DateTime deadLine)
        {
            Contract.Requires(id >= 0 && id < Capacity);

            _writer.WriteShortDate(deadLine, (int)GetEntryDataOffset(id) + OFFSET_ENTRY_DEAD_YEAR);
        }

        #endregion // Public Methods
    }
}
